package com.realshare.frame.util.safety.china;

import com.realshare.frame.util.safety.Base64Utils;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;

import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class Sm2Util extends GmBaseUtil {
    //////////////////////////////////////////////////////////////////////////////////////
    /*
     * 以下为SM2推荐曲线参数
     */
    private final static BigInteger SM2_ECC_P = new BigInteger(
            "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16);
    private final static BigInteger SM2_ECC_A = new BigInteger(
            "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16);
    private final static BigInteger SM2_ECC_B = new BigInteger(
            "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16);
    private final static BigInteger SM2_ECC_N = new BigInteger(
            "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16);
    private final static BigInteger SM2_ECC_GX = new BigInteger(
            "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16);
    private final static BigInteger SM2_ECC_GY = new BigInteger(
            "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16);
    private static final ECCurve CURVE = new ECCurve.Fp(SM2_ECC_P, SM2_ECC_A, SM2_ECC_B, null, null);
    private static final ECPoint G_POINT = CURVE.createPoint(SM2_ECC_GX, SM2_ECC_GY);
    private static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, G_POINT, SM2_ECC_N, BigInteger.ONE);
    //////////////////////////////////////////////////////////////////////////////////////

    private static final int SM3_DIGEST_LENGTH = 32;

    /**
     * 获取公钥的key
     */
    private static final String PUBLIC_KEY = "Sm2PublicKey";

    /**
     * 获取私钥的key
     */
    private static final String PRIVATE_KEY = "Sm2PrivateKey";

    /**
     * <p>
     * 获取私钥
     * </p>
     *
     * @param keyMap 密钥对
     * @return 返回私钥
     */
    public static String getPrivateKey(Map<String, String> keyMap) {
        return keyMap.get(PRIVATE_KEY);
    }

    /**
     * <p>
     * 获取公钥
     * </p>
     *
     * @param keyMap 密钥对
     * @return 返回公钥
     */
    public static String getPublicKey(Map<String, String> keyMap) {
        return keyMap.get(PUBLIC_KEY);
    }

    /**
     * 生成ECC密钥对
     *
     * @return ECC密钥对
     */
    public static Map<String, String> generateKeyPair() {
        SecureRandom random = new SecureRandom();
        AsymmetricCipherKeyPair keyPair = BCECUtil.generateKeyPair(DOMAIN_PARAMS, random);
        ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) keyPair.getPrivate();
        ECPublicKeyParameters ecpub = (ECPublicKeyParameters) keyPair.getPublic();
        BigInteger privateKey = ecpriv.getD();
        ECPoint publicKey = ecpub.getQ();
        Map<String, String> keyMap = new HashMap<String, String>();
        keyMap.put(PUBLIC_KEY, Base64Utils.encoder(publicKey.getEncoded(true)));
        keyMap.put(PRIVATE_KEY, Base64Utils.encoder(privateKey.toByteArray()));
        return keyMap;
    }

    /**
     * ECC公钥加密
     *
     * @param data      源数据
     * @param publicKey ECC公钥
     * @return SM2密文，实际包含三部分：ECC公钥、真正的密文、公钥和原文的SM3-HASH值
     */
    public static String encrypt(String data, String publicKey) {
        try {
            byte[] bytes = data.getBytes();
            ECPoint ecPoint = CURVE.decodePoint(Base64Utils.decoder(publicKey));
            ECPublicKeyParameters pubKey = new ECPublicKeyParameters(ecPoint, DOMAIN_PARAMS);
            SM2Engine engine = new SM2Engine();
            ParametersWithRandom pwr = new ParametersWithRandom(pubKey, new SecureRandom());
            engine.init(true, pwr);
            return Base64Utils.encoder(engine.processBlock(bytes, 0, bytes.length));
        } catch (InvalidCipherTextException e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * ECC私钥解密
     *
     * @param data       SM2密文，实际包含三部分：ECC公钥、真正的密文、公钥和原文的SM3-HASH值
     * @param privateKey ECC私钥
     * @return 原文
     */
    public static String decrypt(String data, String privateKey) {
        try {
            byte[] bytes = Base64Utils.decoder(data);
            BigInteger bigInteger = new BigInteger(Base64Utils.decoder(privateKey));
            ECPrivateKeyParameters priKey = new ECPrivateKeyParameters(bigInteger, DOMAIN_PARAMS);
            SM2Engine engine = new SM2Engine();
            engine.init(false, priKey);
            return new String(engine.processBlock(bytes, 0, bytes.length));
        } catch (InvalidCipherTextException e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 分解SM2密文
     *
     * @param cipherText SM2密文
     * @return Sm2EncryptResult
     */
    public static Sm2EncryptResult parseSm2CipherText(byte[] cipherText) {
        int curveLength = BCECUtil.getCurveLength(DOMAIN_PARAMS);
        return parseSm2CipherText(curveLength, SM3_DIGEST_LENGTH, cipherText);
    }

    /**
     * 分解SM2密文
     *
     * @param curveLength  ECC曲线长度
     * @param digestLength HASH长度
     * @param cipherText   SM2密文
     * @return Sm2EncryptResult
     */
    public static Sm2EncryptResult parseSm2CipherText(int curveLength, int digestLength, byte[] cipherText) {
        byte[] c1 = new byte[curveLength * 2 + 1];
        System.arraycopy(cipherText, 0, c1, 0, c1.length);
        byte[] c2 = new byte[cipherText.length - c1.length - digestLength];
        System.arraycopy(cipherText, c1.length, c2, 0, c2.length);
        byte[] c3 = new byte[digestLength];
        System.arraycopy(cipherText, c1.length + c2.length, c3, 0, c2.length);
        Sm2EncryptResult result = new Sm2EncryptResult();
        result.setC1(c1);
        result.setC2(c2);
        result.setC3(c3);
        result.setCipherText(cipherText);
        return result;
    }

    /**
     * ECC私钥签名
     * 不指定withId，则默认withId为字节数组:"1234567812345678".getBytes()
     *
     * @param data       源数据
     * @param privateKey ECC私钥
     * @return 签名
     */
    public static String sign(String data, String privateKey) {
        return sign(data, privateKey, null);
    }

    /**
     * ECC私钥签名
     *
     * @param privateKey ECC私钥
     * @param withId     可以为null，若为null，则默认withId为字节数组:"1234567812345678".getBytes()
     * @param data       源数据
     * @return 签名
     */
    public static String sign(String data, String privateKey, byte[] withId) {
        try {
            byte[] dataBytes = Base64Utils.decoder(data);
            BigInteger bigInteger = new BigInteger(Base64Utils.decoder(privateKey));
            ECPrivateKeyParameters priKey = new ECPrivateKeyParameters(bigInteger, DOMAIN_PARAMS);
            SM2Signer signer = new SM2Signer();
            CipherParameters param;
            ParametersWithRandom pwr = new ParametersWithRandom(priKey, new SecureRandom());
            if (withId != null) {
                param = new ParametersWithID(pwr, withId);
            } else {
                param = pwr;
            }
            signer.init(true, param);
            signer.update(dataBytes, 0, dataBytes.length);
            return Base64Utils.encoder(signer.generateSignature());
        } catch (CryptoException e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * ECC公钥验签
     * 不指定withId，则默认withId为字节数组:"1234567812345678".getBytes()
     *
     * @param publicKey ECC公钥
     * @param data      源数据
     * @param sign      签名
     * @return 验签成功返回true，失败返回false
     */
    public static boolean verify(String data, String publicKey, String sign) {
        return verify(data, publicKey, null, sign);
    }

    /**
     * ECC公钥验签
     *
     * @param publicKey ECC公钥
     * @param withId    可以为null，若为null，则默认withId为字节数组:"1234567812345678".getBytes()
     * @param data      源数据
     * @param sign      签名
     * @return 验签成功返回true，失败返回false
     */
    public static boolean verify(String data, String publicKey, byte[] withId, String sign) {
        byte[] bytes = Base64Utils.decoder(data);
        ECPoint ecPoint = CURVE.decodePoint(Base64Utils.decoder(publicKey));
        ECPublicKeyParameters pubKey = new ECPublicKeyParameters(ecPoint, DOMAIN_PARAMS);
        SM2Signer signer = new SM2Signer();
        CipherParameters param;
        if (withId != null) {
            param = new ParametersWithID(pubKey, withId);
        } else {
            param = pubKey;
        }
        signer.init(false, param);
        signer.update(bytes, 0, bytes.length);
        return signer.verifySignature(Base64Utils.decoder(sign));
    }

    public static void main(String[] args) {
        Date date = new Date();
        Map<String, String> keyMap = generateKeyPair();
        if (keyMap != null) {
            String source = "一醉汉不慎从三楼掉下，引来路人围观，一警察过来：发生了什么事？醉汉：不清楚，我也是刚到！";
            String publicKey = getPublicKey(keyMap);
            String privateKey = getPrivateKey(keyMap);
            System.err.println("公钥：" + publicKey);
            System.err.println("私钥： " + privateKey);
            test(publicKey, privateKey, source);
            System.out.println("加密耗时：" + (new Date().getTime() - date.getTime()) + "毫秒");
        }
    }

    private static void test(String publicKey, String privateKey, String source) {
        System.err.println("公钥加密——私钥解密");
        System.out.println("原文字：" + source);
        System.out.println("公钥：" + publicKey);
        System.out.println("私钥：" + privateKey);
        String encodedData = encrypt(source, publicKey);
        System.out.println("sm2加密后文字：" + encodedData);
        String decodedData = decrypt(encodedData, privateKey);
        System.out.println("sm2解密后文字: " + decodedData);

        System.err.println("私钥签名——公钥验证签名");
        String sign = sign(encodedData, privateKey);
        System.err.println("签名:" + sign);
        boolean status = verify(encodedData, publicKey, sign);
        System.err.println("验证结果:" + status);
    }

}
